package com.mjlf.framework.util;

import com.mjlf.framework.Type.ScopeType;
import com.mjlf.framework.annotation.Autowired;
import com.mjlf.framework.annotation.Component;
import com.mjlf.framework.annotation.Value;
import com.mjlf.framework.annotation.mydata.Mapper;
import com.mjlf.framework.aop.AdviseMethodManager;
import com.mjlf.framework.aop.ProxyManager;
import com.mjlf.framework.mydata.ConnectionManager;
import com.mjlf.framework.mydata.MapperProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

/**
 * @auther a123
 * @create 2018-08-25 16:11
 * @desc 实例管理类, 在初始化该类之前必须先加载主配置，也就是ConfigUitl
 */
public final class BeanManager {

    private static Logger logger = LoggerFactory.getLogger(BeanManager.class);

    private final static Map<Class<?>, Object> BEAN_MAP = new HashMap<>();
    private final static Stack<Class<?>> creatingClass = new Stack<>();

    public static void init() throws SQLException, ClassNotFoundException {
        ConfigUtil.readConfig(BeanManager.class
                .getResource("/mjlf.properties")
                .getPath());
        ClassManager.init();
        AdviseMethodManager.initAdviseMethod();
        ConnectionManager.init();
        Set<Class<?>> beanClassSet = ClassManager.getClassSet(Component.class);
        beanClassSet.stream().forEach(cls -> {
            createBean(cls);
        });
    }

    /**
     * 创建bean
     * 这里的创建实例， 只能创建有标记@componed注解的类实例
     * 这个过程中需要当心类中是否出现循环引用的情况
     * 同时如果创建的bean要求是单例模式的话，如果在bean中有已经创建好的实例， 直接从库中取即可， 否则需要先创建实例， 然后保存到库中
     * 如果不是单例模式， 直接生成新的实例对象， 然后实现依赖注入
     *
     * @param cls
     *
     * @return
     */
    public static Object createBean(Class<?> cls) {
        Object obj;
        try {
            Component component = (Component) ClassManager.getAnn(cls, Component.class, null);
            if (component == null) {
                throw new Exception("未标记@Component,服务不能对该类进行实例化");
            }

            //判断当前是否已经出现循环引用的情况,如果出现这种情况，抛出异常，做出提示
            if (!creatingClass.isEmpty() && creatingClass.contains(cls)) {
                Class errClass = creatingClass.pop();
                while (errClass != null) {
                    logger.info("循环引用:" + errClass.getName());
                    if (!creatingClass.isEmpty()) {
                        errClass = creatingClass.pop();
                    } else {
                        errClass = null;
                    }
                }
                throw new Exception("类中属性出现循环引用");
            }

            //在bean生成栈中添加该class，防止循环引用
            creatingClass.push(cls);

            //判断是不是单例模式，如果是单例模式， 在bean管理库中查找是否已经存在该实例，如果不存在，则生成实例并保持到bean管理库中
            if (component.scope().equals(ScopeType.SINGLE)) {
                if (BEAN_MAP.containsKey(cls)) {
                    obj = BEAN_MAP.get(cls);
                } else {
                    //是单例模式， 但是在库中尚未存在这样的实例对象，需要重新创建，同时需要给新创建的对象注入由@Autowired属性
                    //最后将实例化好的对象放到bean容器中
                    obj = creteNewObj(cls);
                    BEAN_MAP.put(cls, obj);
                }
            } else {
                obj = creteNewObj(cls);
                setField(obj);
            }
        } catch (Exception e) {
            logger.error("创建实例失败", e);
            throw new RuntimeException(e);
        }
        //是否栈中的该当前class，表示这个类可以正常被实例化
        creatingClass.pop();
        return obj;
    }

    public static Object creteNewObj(Class<?> cls) throws Exception {
        Object obj = null;
        if (ClassManager.needProxy(cls)) {
            obj = ProxyManager.createProxy(cls);
        } else if(cls.isAnnotationPresent(Mapper.class)){
            obj = MapperProxy.createProxy(cls);
        }else {
            obj = ReflectionUtil.newInstance(cls);
        }
        setField(obj);
        return obj;
    }

    /**
     * 给实例对象设置属性
     *
     * @param object
     */
    public static void setField(Object object) throws Exception {
        if (object == null) {
            throw new Exception("需要设置属性的对象不能为空");
        }
        Field[] fields = null;
        if(object.getClass().getName().contains("EnhancerByCGLIB")){
            fields = object.getClass().getSuperclass().getDeclaredFields();
        }else {
            fields = object.getClass().getDeclaredFields();
        }
//        if(object instanceof LoginController){
//            for(Field field : fields){
//                System.out.println("LoginController Filed Class: " + field.getType().getName());
//            }
//        }
        try {
            for (Field field : fields) {
                //<editor-fold desc="设置由@Value标记的属性">
                if (field.isAnnotationPresent(Value.class)) {
                    String keyName = field.getAnnotation(Value.class).value();
                    Class valueClass = field.getAnnotation(Value.class).cls();
                    if (!ConfigUtil.CONFIG_VALUE.containsKey(keyName)) {
                        throw new RuntimeException("配置中无法找到配置 :" + keyName);
                    }
                    Object valueObj = ConfigUtil.getValue(keyName);
                    field.setAccessible(true);
                    switch (valueClass.getSimpleName()) {
                        case "String":
                            field.set(object, valueObj);
                            break;
                        case "Integer":
                            field.setInt(object, Integer.parseInt(valueObj.toString()));
                            break;
                        case "Double":
                            field.setDouble(object, Double.parseDouble(valueObj.toString()));
                            break;
                        case "Long":
                            field.setLong(object, Long.parseLong(valueObj.toString()));
                            break;
                        case "Boolean":
                            field.setBoolean(object, Boolean.getBoolean(valueObj.toString()));
                            break;
                        default:
                            throw new RuntimeException("@Value cls 设置无效， 支持Boolean.class, String.class, " +
                                    "Integer.class, Double.class, Long.class");
                    }
                }
                //</editor-fold>

                //注入由@Autowired标记的属性
                if (field.isAnnotationPresent(Autowired.class)) {
                    Class fieldClass = field.getType();
                    Object fieldValue = null;
                    //判断该属性类上是否标注了@Componed, 如果没有标注，抛出异常，提示无法进行注入
                    if (ClassManager.getAnn(fieldClass, Component.class, null) == null) {
                        throw new Exception("该属性对应的类未标注@Componed, 无法进行依赖注入 : " + fieldClass.getName());
                    }
                    Component component = (Component) ClassManager.getAnn(fieldClass, Component.class, null);
                    //判断是不是单例模式，如果是单例模式，首先到bean管理库中查找是否已经实例了该属性对象，否则实例化当前属性对象，然后将其
                    //保存到bean库中
                    //需要注意的是， 这里可能会出现循环依赖的状况， 需要考虑怎么处理， 并且做好提示
                    if (component != null) {
                        fieldValue = getBean(fieldClass);
                    }
//                    if(object instanceof LoginController){
//                        System.out.println("LoginController Field : " + fieldValue.getClass().getName());
//                    }
                    //属性设置
                    if (fieldValue != null) {
                        ReflectionUtil.setField(object, field, fieldValue);
                    } else {
                        throw new Exception("不能给对象属性设置值为空");
                    }
                }
            }
        } catch (Exception e) {
            logger.error("注入实例属性失败", e);
            throw new RuntimeException(e);
        }
    }

    public static Map<Class<?>, Object> getBeanMap() {
        return BEAN_MAP;
    }

    /**
     * 获取指定实例
     *
     * @param cls
     * @param <T>
     *
     * @return
     */
    public static <T> T getBean(Class<T> cls) throws Exception {
        if (ClassManager.getAnn(cls, Component.class, null) == null) {
            throw new Exception("不能获取该类实例对象， 该类没有标注@Componed : " + cls.getName());
        }
        Component component = (Component) ClassManager.getAnn(cls, Component.class, null);
        if (component.scope().equals(ScopeType.SINGLE)) {
            if (!BEAN_MAP.containsKey(cls)) {
                return (T) createBean(cls);
            }
            return (T) BEAN_MAP.get(cls);
        } else {
            return (T) createBean(cls);
        }
    }
}
